package cn.xzc.job.config;

import cn.xzc.job.annotation.TimerMethod;
import cn.xzc.job.annotation.TimerMethods;
import cn.xzc.job.cocurrent.CustomizableThreadFactory;
import cn.xzc.job.cocurrent.ThreadPoolJobScheduler;
import cn.xzc.job.constant.Constant;
import cn.xzc.job.enums.TimerMethodType;
import cn.xzc.job.pojo.ScheduledJob;
import cn.xzc.job.pojo.ScheduledMethodRunnable;
import cn.xzc.job.util.ThrowableUtil;
import cn.xzc.job.type.cron.CronJob;
import cn.xzc.job.type.cron.CronTrigger;
import cn.xzc.job.type.interval.FixedDelayJob;
import cn.xzc.job.type.interval.FixedRateJob;
import cn.xzc.job.util.*;

import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author xzc
 * @Classname ScheduledJobInit
 * @Description 定时任务初始化执行操作
 * @Date 2024/2/17 20:58
 */
public class ScheduledJobInit {
    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ScheduledJobInit.class);
    private final ScheduledJobRegistrar registrar;
    /**
     * 存储所有的定时任务
     */
    private final Map<Method, Set<ScheduledJob>> scheduledJobs = new HashMap<>(16);


    public ScheduledJobInit() {
        this.registrar = new ScheduledJobRegistrar();
    }

    public ScheduledJobInit(ScheduledJobRegistrar registrar) {
        this.registrar = registrar;
    }

    /**
     * 反射中的Method的条件
     *
     * @return method是否符合条件
     */
    private static Predicate<Method> methodPredicate() {
        return method -> {
            //判断方法是否是对应定时任务的方法【包含对应注解、不包含参数】
            if (method.getParameterCount() > 0) {
                return false;
            }
            boolean containTimerMethod = ClassUtil.containAnnotation(method, TimerMethod.class);
            boolean containTimerMethods = ClassUtil.containAnnotation(method, TimerMethods.class);
            if (containTimerMethod && containTimerMethods) {
                log.error("定时任务方法上不能同时使用TimerMethods和TimerMethod");
                return false;
            }
            if (!containTimerMethod && !containTimerMethods) {
                return false;
            }
            //判断方法是否是static方法
            TimerMethod timerMethod = method.getDeclaredAnnotation(TimerMethod.class);
            if (Objects.nonNull(timerMethod) && TimerMethodType.errorAnnotation(timerMethod)) {
                log.error("method:{},定时任务注解@timerMethod使用不规范！请重新注释!", method.getName());
                return false;
            }
            TimerMethods timerMethods = method.getDeclaredAnnotation(TimerMethods.class);
            if (Objects.nonNull(timerMethods) && TimerMethodType.errorAnnotation(timerMethods)) {
                log.error("method:{},定时任务注解@timerMethods使用不规范！请重新注释!", method.getName());
                return false;
            }
            //判断方法是否是static方法
            if (Modifier.isStatic(method.getModifiers())) {
                return true;
            }
            try {
                //尝试创建无参构造器对象
                method.getDeclaringClass().getDeclaredConstructor().newInstance();
                return true;
            } catch (Exception e) {
                //创建对应方法类对象失败
                return false;
            }
        };
    }

    /**
     * 根据定时任务数量和实际的可用处理器数设置线程池线程池大小
     *
     * @param methodNum 定时任务数量
     * @return 线程池大小
     */
    private static int getPoolSize(int methodNum) {
        int parallelism = Runtime.getRuntime().availableProcessors();
        int poolSize = parallelism * 2;
        if (poolSize < methodNum) {
            poolSize = poolSize * 2;
        }
        return Math.max(poolSize, 1);
    }

    /**
     * 创建定时任务线程池
     *
     * @param methodNum 定时任务数量
     * @return 定时任务线程池
     */
    private static ThreadPoolJobScheduler createThreadPool(int methodNum) {
        int poolSize = getPoolSize(methodNum);
        //当前系统可用的处理器数
        String threadNamePrefix = "timer-pool-";
        ThreadPoolJobScheduler threadPoolJobScheduler = new ThreadPoolJobScheduler();
        threadPoolJobScheduler.setPoolSize(poolSize);
        threadPoolJobScheduler.setThreadNamePrefix(threadNamePrefix);
        threadPoolJobScheduler.setAwaitTerminationSeconds(60);
        //不强制关闭线程
        threadPoolJobScheduler.setWaitForTasksToCompleteOnShutdown(true);
        //在线程池执行shutdown方法后不继续执行定时任务
        threadPoolJobScheduler.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
        //在线程池执行shutdown方法后，需要等待当前正在等待的任务的和正在运行的任务被执行完，然后进程被销毁
        threadPoolJobScheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(true);
        //不设置异常处理策略：代码中会自动选择异常处理策略
        //remove-on-cancel mode
        threadPoolJobScheduler.setRemoveOnCancelPolicy(true);
        CustomizableThreadFactory threadFactory = new CustomizableThreadFactory();
        threadFactory.setThreadNamePrefix(threadNamePrefix);
        threadPoolJobScheduler.setThreadFactory(threadFactory);
        //设置线程池拒绝策略
        threadPoolJobScheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        threadPoolJobScheduler.init();
        return threadPoolJobScheduler;
    }

    /**
     * 统一时间单位i
     *
     * @param value    时间值
     * @param timeUnit 时间单位
     * @return 转换成时间单位为毫秒的时间
     */
    private static long convertToMillis(long value, TimeUnit timeUnit) {
        return TimeUnit.MILLISECONDS.convert(value, timeUnit);
    }

    public void init() {
        log.info("------ init scheduled job ------");
        //1. 获取所有定时任务
        Set<AnnotatedElement> methodAnnotatedElements = ReflectUtil.scan(Constant.JOB_METHOD_SCAN_PACKAGE, null, methodPredicate());
        if (CollectionUtils.isEmpty(methodAnnotatedElements)) {
            log.warn("------ no scheduled method ------");
            return;
        }
        //2. 获取定时任务相关信息
        Set<Method> methods = methodAnnotatedElements.stream().map(element -> (Method) element).collect(Collectors.toSet());
        Map<Method, Set<TimerMethod>> annotatedMethods = methods.stream().filter(method -> Objects.nonNull(method) && (method.isAnnotationPresent(TimerMethod.class) || method.isAnnotationPresent(TimerMethods.class))).
                collect(Collectors.toMap(method -> method, method -> {
                    TimerMethod timerMethod = method.getDeclaredAnnotation(TimerMethod.class);
                    TimerMethods timerMethods = method.getDeclaredAnnotation(TimerMethods.class);
                    if (Objects.nonNull(timerMethod)) {
                        return Collections.singleton(timerMethod);
                    } else if (Objects.nonNull(timerMethods)) {
                        return new HashSet<>(Arrays.asList(timerMethods.value()));
                    }
                    return Collections.emptySet();

                }));
        if (MapUtils.isEmpty(annotatedMethods)) {
            log.warn("------ no scheduled method ------");
            return;
        }

        //3. 获取线程池 常规线程池 长时间执行线程池
        ThreadPoolJobScheduler curThreadPool = createThreadPool(methods.size());
        registrar.setJobScheduler(curThreadPool);
        annotatedMethods.forEach((method, timerMethods) ->
                timerMethods.forEach(timerMethod -> processScheduled(method, timerMethod)));
        registrar.init();
        log.info("------ init scheduled job success ------");
    }

    /**
     * 将单个定时任务注解添加到线程池中执行的定时任务
     *
     * @param method      方法
     * @param timerMethod 定时任务注解·
     */
    private void processScheduled(Method method, TimerMethod timerMethod) {
        Object target = null;
        try {
            if (!Modifier.isStatic(method.getModifiers())) {
                target = method.getDeclaringClass().getDeclaredConstructor().newInstance();
            }
            Runnable runnable = new ScheduledMethodRunnable(target, method);
            long initialDelay = convertToMillis(timerMethod.initialDelay(), timerMethod.timeUnit());
            initialDelay = initialDelay < 0 ? 0 : initialDelay;
            ScheduledJob scheduledJob;
            switch (Objects.requireNonNull(TimerMethodType.getTimerMethodType(timerMethod))) {
                case FIXED_RATE:
                    long fixedRate = convertToMillis(timerMethod.fixedRate(), timerMethod.timeUnit());
                    scheduledJob = this.registrar.scheduleFixedRateJob(new FixedRateJob(runnable, fixedRate, initialDelay));
                    break;
                case FIXED_DELAY:
                    long fixedDelay = convertToMillis(timerMethod.fixedDelay(), timerMethod.timeUnit());
                    scheduledJob = this.registrar.scheduleFixedDelayJob(new FixedDelayJob(runnable, fixedDelay, initialDelay));
                    break;
                case CRON:
                    TimeZone timeZone = StringUtils.isBlank(timerMethod.zone()) ? TimeZone.getDefault() : TimeZone.getTimeZone(timerMethod.zone());
                    scheduledJob = this.registrar.scheduleCronJob(new CronJob(runnable, new CronTrigger(timerMethod.cron(), timeZone)));
                    break;
                default:
                    throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "'");
            }
            synchronized (this.scheduledJobs) {
                Set<ScheduledJob> regTasks = this.scheduledJobs.computeIfAbsent(method, key -> new LinkedHashSet<>(3));
                regTasks.add(scheduledJob);
            }
        } catch (Exception e) {
            //基本不会走这里，上面已经过滤了这个步骤
            log.error(":\n{}", ThrowableUtil.getThrowableMessage(e));
            throw new IllegalStateException(
                    "Encountered invalid @Scheduled method '" + method.getName() + "': " + e.getMessage());
        }
    }


    /**
     * 获取所有的定时任务
     *
     * @return 所有定时任务
     */
    public Set<ScheduledJob> getScheduledJobs() {
        Set<ScheduledJob> result = new LinkedHashSet<>();
        synchronized (this.scheduledJobs) {
            Collection<Set<ScheduledJob>> allTasks = this.scheduledJobs.values();
            for (Set<ScheduledJob> tasks : allTasks) {
                result.addAll(tasks);
            }
        }
        result.addAll(this.registrar.getScheduledJobs());
        return result;
    }

    /**
     * 根据方法销毁相关联的线程
     *
     * @param method 定时任务
     */
    public void destroy(Method method) {
        Set<ScheduledJob> tasks;
        synchronized (this.scheduledJobs) {
            tasks = this.scheduledJobs.remove(method);
        }
        if (tasks != null) {
            for (ScheduledJob task : tasks) {
                task.cancel();
            }
        }
    }

    /**
     * 线程池销毁时执行
     */
    public void destroy() {
        synchronized (this.scheduledJobs) {
            Collection<Set<ScheduledJob>> allTasks = this.scheduledJobs.values();
            for (Set<ScheduledJob> tasks : allTasks) {
                for (ScheduledJob task : tasks) {
                    task.cancel();
                }
            }
            this.scheduledJobs.clear();
        }
        this.registrar.destroy();
    }
}
